/*
 * PlayCanvas.java
 *
 * Created on 21. November 2006, 13:46
 */
package com.mopius.game;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Random;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Font;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.game.GameCanvas;
import javax.microedition.lcdui.game.LayerManager;
import javax.microedition.lcdui.game.Sprite;
import javax.microedition.lcdui.game.TiledLayer;
import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordStoreException;
import javax.microedition.rms.RecordStoreNotFoundException;

/**
 *
 * @author Andreas Jakl
 */
public class PlayCanvas extends GameCanvas implements Runnable, CommandListener
{
    /** Reference to the midlet to send exit events. */
    private SchlaboMIDlet iMidlet;
    /** Command to exit the game. */
    private Command cmdExit;
    /** Width of the screen. */
    private int iWidth;
    /** Height of the screen. */
    private int iHeight;
    /** Layer manager for managing our graphics objects (sprites and layers). */
    private LayerManager iLayerManager;
    /** Image of the player. */
    private Sprite iPlayerSprite;
    /** Image of the enemy. */
    private Sprite iEnemySprite;
    /** Image for a single bullet (shot). */
    private Sprite iBulletSprite;
    /** Layer containing the background. */
    private TiledLayer iBgLayer;
    /** Current score of the player. */
    private int iScore;
    /** true if a shot is currently flying around. Only one shot can be active at a time. */
    private boolean iIsShotActive;
    /** Current x-coordinate of the player (center of the image) */
    private int iX;
    /** Current y-coordinate of the player (center of the image) */
    private int iY;
    /** How many pixels in positive or negative direction should the enemy move every frame. */
    private int iEnemyMovement;
    /** The target that the enemy should reach with its current movment. It will think about
     * a new target once it has reached this position. */
    private int iEnemyTargetX;
    /**
     * Tile-IDs for the background map.
     * Created using the mappy-tool.
     */
    private final static char bg_map[] =
    {
        2, 2, 2, 2, 5, 1, 1, 1, 1, 3, 2, 2, 2, 2, 2,
        10, 2, 2, 2, 5, 1, 1, 1, 1, 3, 2, 2, 2, 2, 2,
        3, 2, 2, 9, 11, 1, 1, 1, 1, 3, 2, 2, 2, 2, 2,
        12, 6, 6, 11, 1, 1, 1, 1, 1, 12, 6, 10, 2, 2, 2,
        1, 1, 1, 1, 1, 1, 1, 13, 14, 1, 1, 12, 10, 2, 2,
        1, 1, 1, 1, 1, 1, 1, 12, 11, 1, 1, 1, 12, 6, 6,
        13, 4, 14, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        3, 9, 11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        12, 11, 1, 1, 13, 4, 4, 4, 4, 14, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 3, 2, 2, 2, 2, 8, 4, 4, 14, 1, 1,
        1, 1, 1, 1, 12, 10, 2, 9, 10, 2, 2, 2, 5, 1, 1,
        1, 1, 1, 1, 13, 7, 2, 8, 7, 2, 2, 2, 5, 1, 1,
        1, 1, 1, 1, 3, 2, 2, 2, 2, 2, 9, 6, 11, 1, 1,
        1, 1, 1, 13, 7, 2, 2, 2, 2, 2, 5, 13, 14, 1, 1,
        1, 1, 1, 3, 2, 2, 2, 2, 2, 2, 8, 7, 5, 1, 1
    };
    /** True if the game is running. If the thread should stop, set this to false by calling stop(). */
    private boolean iIsActive = false;

    /** Creates a new instance of PlayCanvas */
    public PlayCanvas(SchlaboMIDlet aMidlet)
    {
        super(true);
        // User full screen mode for the game
        setFullScreenMode(true);
        // Save reference to the midlet to send events to it
        iMidlet = aMidlet;
        // Create the command so that the player can exit the game
        cmdExit = new Command("Exit", Command.EXIT, 0);
        this.addCommand(cmdExit);
        setCommandListener(this);
    }

    /**
     * Initialize the game, set member variables to the states we want and load all graphics.
     */
    public void init()
    {
        // Get properties of the screen
        iWidth = getWidth();
        iHeight = getHeight();
        // Initialize score with 0
        iScore = 0;
        // No shot is active right from the beginning
        iIsShotActive = false;
        // Initialize the enemy
        iEnemyMovement = 0;
        iEnemyTargetX = iWidth / 2;
        try
        {
            // Load player sprite
            iPlayerSprite = new Sprite(Image.createImage("/player.png"));
            // Set reference pixel to the center of the player sprite
            iPlayerSprite.defineReferencePixel(iPlayerSprite.getWidth() / 2, iPlayerSprite.getHeight() / 2);
            // Load enemy sprite
            iEnemySprite = new Sprite(Image.createImage("/enemy.png"));
            iEnemySprite.defineReferencePixel(iEnemySprite.getWidth() / 2, iEnemySprite.getHeight() / 2);
            // Load bullet sprite
            iBulletSprite = new Sprite(Image.createImage("/bullet.png"));
            iBulletSprite.defineReferencePixel(iBulletSprite.getWidth() / 2, iBulletSprite.getHeight() / 2);
            // Bullet will not be visible right from the beginning
            iBulletSprite.setVisible(false);
            // Load background layer
            iBgLayer = initBackground();
        } catch (IOException ex)
        {
            ex.printStackTrace();
        }

        // Now that all sprites and layers are loaded, create the layer manager
        // and let it manage all our graphic objects
        iLayerManager = new LayerManager();
        // Order of appending layers defines z-order (for visibility)
        iLayerManager.append(iPlayerSprite);
        iLayerManager.append(iEnemySprite);
        iLayerManager.append(iBulletSprite);
        iLayerManager.append(iBgLayer);

        // Define initial positions
        iX = iWidth / 2;
        iY = iHeight - iPlayerSprite.getHeight();
        // Set player to the horizontal center of the screen and in the lower part of it
        iPlayerSprite.setRefPixelPosition(iX, iY);

        // The enemy should be centered as well, but in the upper part of the screen
        iEnemySprite.setRefPixelPosition(iX, 0 + iEnemySprite.getHeight());

        // Start our game loop
        iIsActive = true;
        Thread t = new Thread(this);
        t.start();
    }

    public void run()
    {
        while (iIsActive)
        {
            // Handle keys - get current state
            // Process the keys to move the player or fire a shot
            processKeys(getKeyStates());

            // Update world (move bullets, check for collisions)
            updateWorld();

            // Do drawing (draw the current game state to the back buffer)
            drawWorld();

            // Redraw Screen (send back buffer to the visible screen)
            flushGraphics();

            // Give other threads a chance to run
            // Of course a real game should measure the time and between
            // each run of the game loop and update the world accordingly.
            // This version will run at a different speed on every handset.
            try
            {
                Thread.sleep(20);
            } catch (InterruptedException ex)
            {
                ex.printStackTrace();
            }
        }
    }

    /**
     * Do the appropriate action according to the specified game key states.
     *
     * @param aKeyStates game key states that we got from the game canvas.
     */
    private void processKeys(int aKeyStates)
    {
        switch (aKeyStates)
        {
            case LEFT_PRESSED:
                // Move the player left by three pixels.
                movePlayer(-3);
                break;
            case RIGHT_PRESSED:
                // Move the player right by three pixels.
                movePlayer(3);
                break;
            case UP_PRESSED:
                break;
            case DOWN_PRESSED:
                break;
            case FIRE_PRESSED:
                // Only allow to fire a new shot if none is currently active
                if (!iIsShotActive)
                {
                    modifyBullet(true);
                }
                break;
        }
    }

    /**
     * Logic for updating the state of the world. Moves bullets and checks for collisions.
     */
    private void updateWorld()
    {
        // Move enemy
        moveEnemy();
        if (iIsShotActive)
        {
            // Move bullet
            iBulletSprite.move(0, -3);
            // Check if the bullet has now moved out of the screen
            if (iBulletSprite.getRefPixelY() < 0)
            {
                // Bullet moved out of the screen
                modifyBullet(false);
            } else
            {
                // Bullet is still visible, so do a collision check
                if (iBulletSprite.collidesWith(iEnemySprite, true))
                {
                    // We hit the target
                    iScore += 7;
                    modifyBullet(false);
                }
            }
        }
    }

    /**
     * Move the player on the x-axis by the specified movement amount.
     *
     *
     * @param aDistance how many pixels to move right (positive number)
     * or left (negative number).
     */
    private void movePlayer(int aDistance)
    {
        iX += aDistance;
        // Make sure the player can't move out of the screen
        if (iX < iPlayerSprite.getWidth() / 2)
        {
            iX = iPlayerSprite.getWidth() / 2;
        } else if (iX > iWidth - iPlayerSprite.getWidth() / 2)
        {
            iX = iWidth - iPlayerSprite.getWidth() / 2;
        }
        // Update the position of the sprite
        iPlayerSprite.setRefPixelPosition(iX, iY);
    }

    /**
     * Do a random movement of the enemy
     */
    private void moveEnemy()
    {
        final int enemyX = iEnemySprite.getRefPixelX();
        final int enemyY = iEnemySprite.getRefPixelY();
        // Check if the enemy has reached its target
        if (enemyX == iEnemyTargetX)
        {
            // Think of a new movement target
            Random rnd = new Random();
            iEnemyTargetX = rnd.nextInt(iWidth - iPlayerSprite.getWidth());
            iEnemyTargetX += iPlayerSprite.getWidth() / 2;
            // Set the direction (should the enemy move right or left?
            iEnemyMovement = (enemyX < iEnemyTargetX) ? 1 : -1;
            // If the new position is the same as the old one, the enemy should
            // stand still (at least for one frame)
            if (enemyX == iEnemyTargetX)
            {
                iEnemyMovement = 0;
            }
        } else
        {
            // We're still moving towards a movement target, so just move the
            // sprite by the specified amount of difference
            iEnemySprite.move(iEnemyMovement, 0);
        }
    }

    /**
     * Add or remove the currently active bullet.
     * This either sets it to invisible and changes that iShotActive status variable or
     * sets it to active and takes care of positioning and decreasing the score.
     */
    private void modifyBullet(boolean aActivate)
    {
        if (!aActivate)
        {
            iIsShotActive = false;
            iBulletSprite.setVisible(false);
        } else
        {
            // Now a shot is active
            iIsShotActive = true;
            // Shooting decreases our score, hitting the target increases it
            iScore -= 3;
            // Set the start position of the bullet to the center of the player
            iBulletSprite.setRefPixelPosition(iPlayerSprite.getRefPixelX(), iPlayerSprite.getRefPixelY());
            // Make the layer visible
            iBulletSprite.setVisible(true);
        }
    }

    /**
     * Do the actual drawing work to the back buffer.
     */
    private void drawWorld()
    {
        Graphics g = getGraphics();
        // Draw game contents
        iLayerManager.paint(g, 0, 0);
        // Draw score text
        g.setColor(255, 255, 255);
        g.setFont(Font.getFont(Font.FACE_PROPORTIONAL, Font.STYLE_BOLD, Font.SIZE_LARGE));
        g.drawString("Score: " + iScore, 10, 10, Graphics.TOP | Graphics.LEFT);
    }

    /**
     * Load the background tile map that is composed of several small bitmaps.
     */
    private TiledLayer initBackground() throws IOException
    {
        final int tileCols = 15;
        final int tileRows = 15;
        final int tileSizePixels = 32;
        // Create the tiled layer for the background
        TiledLayer bgLayer = new TiledLayer(tileCols, tileRows, Image.createImage("/tiles.png"), tileSizePixels, tileSizePixels);
        // Assign tiles according to the 1D-array
        for (int i = 0; i < bg_map.length; i++)
        {
            int column = i % tileRows;
            int row = (i - column) / tileCols;
            bgLayer.setCell(column, row, bg_map[i]);
        }
        return bgLayer;
    }

    /**
     * Stop and with that deletes the thread after its next execution.
     */
    public void stop()
    {
        iIsActive = false;
    }

    /**
     * Handle commands sent by the framework.
     */
    public void commandAction(Command command, Displayable displayable)
    {
        if (command == cmdExit)
        {
            iMidlet.exit();
        }
    }

    public void saveSettings()
    {
    }

    public void loadSettings()
    {
    }
}
